package fnsvr

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

// 唯一键冲突
var ErrMongoUniqueKeyConflict = errors.New("mongo: unique key conflict")

// 写入异常
var ErrMongoWriteException = errors.New("mongo: write exception")

// 网络错误
var ErrMongoDeadlineExceeded = errors.New("mongo: dead line exceeded")

// 命令权限错误
var ErrMongoCommandError = errors.New("mongo: command error")

// 记录不存在
var ErrMongoNoDocuments = mongo.ErrNoDocuments

/*
ping
Log().Error("DBDBG Mogo.UpdateOne ", "err", err, "filter", filter, "data", updateObj)

err2 := mgo.Cli.Ping(context.Background(), nil)
if err2 != nil {

	return nil, NewDbError(DbErrorNet, "communication error!")
}*/

// mongo错误码转换
func mongoErrConvert(err error) error {

	if err == nil {
		return err
	}

	var writeErr mongo.WriteException
	var cmdErr mongo.CommandError
	if errors.As(err, &writeErr) {
		for _, e := range writeErr.WriteErrors {
			if e.Code == 11000 {
				return ErrMongoUniqueKeyConflict // 唯一键冲突
			}
		}
		return ErrMongoWriteException //写入异常
	} else if errors.Is(err, context.DeadlineExceeded) {
		return ErrMongoDeadlineExceeded //网络错误
	} else if errors.As(err, &cmdErr) {
		return ErrMongoCommandError //命令权限错误
	}

	return err
}

type Mogo struct {
	Cli         *mongo.Client
	Db          *mongo.Database
	collections sync.Map
}

// 初始化数据库
func NewMogo(dbcfg MongoDbConfig) (*Mogo, error) {

	addrs := strings.Split(dbcfg.Host, ",")
	uri := ""
	if len(addrs) > 1 {
		addrStr := strings.Join(addrs, ",")
		uri = "mongodb://" + dbcfg.Username + ":" + strings.ReplaceAll(dbcfg.Password, "@", "%40") + "@" + addrStr + "/admin?replicaSet=" + dbcfg.Mgset
	} else {
		portStr := strconv.Itoa(dbcfg.Port)
		uri = "mongodb://" + dbcfg.Username + ":" + strings.ReplaceAll(dbcfg.Password, "@", "%40") + "@" + dbcfg.Host + ":" + portStr + "/" + dbcfg.DbName + "?authSource=admin"
	}

	Log().Info("InitMgo", "uri", uri)

	cli, db, err := connet(uri, dbcfg.DbName)
	if err != nil {
		Log().Error("InitMgo error!!", "uri", uri, "err", err)
		return nil, err
	}
	inst := &Mogo{
		Cli: cli,
		Db:  db,
	}
	return inst, nil
}

// 初始化连接
func connet(uri, dbname string) (*mongo.Client, *mongo.Database, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri).SetMaxPoolSize(20)) // 连接池
	if err != nil {
		fmt.Println(err)
		return nil, nil, mongoErrConvert(err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		Log().Error("mongo connection", "err", err)
		client.Disconnect(ctx)
		return nil, nil, mongoErrConvert(err)
	}
	db := client.Database(dbname)
	return client, db, nil
}

// 获取一个集合
func (myself *Mogo) GetCollection(collection string) *mongo.Collection {
	collectionInst, ok := myself.collections.Load(collection)
	if !ok {
		collectionInst = myself.Db.Collection(collection)
		myself.collections.Store(collection, collectionInst)
	}
	return collectionInst.(*mongo.Collection)
}

// Close 关闭连接
func (mgo *Mogo) Close() error {
	if mgo.Cli != nil {
		return mongoErrConvert(mgo.Cli.Disconnect(context.Background()))
	}
	return nil
}

// 更新多条记录
func (mgo *Mogo) DeleteMany(collection string, ids []string) error {
	ctx := context.Background()
	col := mgo.GetCollection(collection)

	idMaps := []bson.M{}
	idLen := len(ids)
	for i := 0; i < idLen; i++ {
		idMaps = append(idMaps, bson.M{"_id": ids[i]})
	}
	filter := bson.M{"$or": idMaps}

	//执行删除
	_, err := col.DeleteMany(ctx, filter)
	if err != nil {
		Log().Error("DeleteMany error", "collection", collection, "err", err)
		return mongoErrConvert(err)
	}

	return nil
}

// 更新多条记录
func (mgo *Mogo) UpdateMany(collection string, ids []string, kv map[string]interface{}, bUpsert bool) error {
	ctx := context.Background()
	col := mgo.GetCollection(collection)

	idMaps := []bson.M{}
	idLen := len(ids)
	for i := 0; i < idLen; i++ {
		idMaps = append(idMaps, bson.M{"_id": ids[i]})
	}
	filter := bson.M{"$or": idMaps}

	//执行插入
	var err error
	if bUpsert {
		_, err = col.UpdateMany(ctx, filter, kv, &options.UpdateOptions{Upsert: &bUpsert})
	} else {
		_, err = col.UpdateMany(ctx, filter, kv)
	}
	if err != nil {
		Log().Error("UpdateMany  error", "collection", collection, "err", err)
		return mongoErrConvert(err)
	}

	return nil
}

// 插入多条数据
func (mgo *Mogo) InsertMany(collection string, objs []interface{}) error {
	ctx := context.Background()
	col := mgo.GetCollection(collection)

	//执行插入
	_, err := col.InsertMany(ctx, objs)
	if err != nil {
		Log().Error("insert error", "collection", collection, "err", err)
		return mongoErrConvert(err)
	}

	return nil
}

// 查询分页 page从1起
// var passesList []model.Passes
// err := db.Find("collectionName", bson.M{"uid":"12345678"}, 1, 10, &passesList)
// sort bson.D{{"number",-1}}
func (mgo *Mogo) Find(collection string, selector bson.M, limit, page int64, sort any, schema interface{}, projection bson.M) error {
	c := mgo.GetCollection(collection)

	//初始化
	var findoptions *options.FindOptions = &options.FindOptions{}

	if projection != nil {
		findoptions.Projection = projection
	}

	if limit > 0 {
		findoptions.SetLimit(int64(limit))
		findoptions.SetSkip(int64(limit * (page - 1))) //(limit * page) - page
	}

	findoptions.SetSort(sort)

	cur, err := c.Find(context.Background(), selector, findoptions)
	if err != nil {
		return mongoErrConvert(err)
	}

	defer cur.Close(context.Background())
	err = cur.All(context.Background(), schema)
	return mongoErrConvert(err)
}

// 查询多条，返回查询出的条目数
func (mgo *Mogo) FindMany(collection string, ids []string, targets []interface{}) (int, error) {
	col := mgo.GetCollection(collection)
	idMaps := []bson.M{}
	idLen := len(ids)
	for i := 0; i < idLen; i++ {
		idMaps = append(idMaps, bson.M{"_id": ids[i]})
	}
	filter := bson.M{"$or": idMaps}

	ctx := context.Background()
	result, err := col.Find(ctx, filter)
	if err != nil {
		fmt.Printf("findMany error: %v", err)
		return 0, mongoErrConvert(err)
	} else {
		i := 0
		for result.Next(ctx) {
			//m := make(map[string]interface{})
			//var m interface{}
			result.Decode(targets[i])
			//data, _ := json.Marshal(m)
			//json.Unmarshal(m,targets[i])
			//targets[i] = m
			i++

		}
		return i, nil
	}
}

// FindOne 查询单个
// 没有找到数据 mongo.ErrNoDocuments
func (mgo *Mogo) FindOne(collection, key string, value interface{}, target interface{}) error {
	col := mgo.GetCollection(collection)
	filter := bson.D{{Key: key, Value: value}}

	singleResult := col.FindOne(context.Background(), filter)
	if singleResult.Err() != nil {
		return mongoErrConvert(singleResult.Err())
	} else {
		singleResult.Decode(target)
		return nil
	}
}

// 插入数据, , 返回值(interface{}：插入数据的ID, error）
// 重复写入错误  mongo.IsDuplicateKeyError(err)
// 网络错误 errors.Is(err, context.DeadlineExceeded)
func (mgo *Mogo) InsertOne(collection string, updateObj interface{}) (interface{}, error) {
	ctx := context.Background()
	col := mgo.GetCollection(collection)

	//执行插入
	res, err := col.InsertOne(ctx, updateObj)

	if err != nil {
		return nil, mongoErrConvert(err)
	}

	return res.InsertedID, nil

}

// 更新数据, 返回值(interface{}：被更新数据的ID, error）
func (mgo *Mogo) UpdateOne(collection, key string, value interface{}, updateObj interface{}) (interface{}, error) {
	col := mgo.GetCollection(collection)
	filter := bson.D{{Key: key, Value: value}}
	//YsDbg.Debugf(nil,"DBDBG Mogo.UpdateOne#1 filter:%v data:%v",filter,updateObj)

	//upsert:=true
	//op:= options.UpdateOptions{Upsert:&upsert}

	//执行替换
	ret, err := col.UpdateOne(context.Background(), filter, updateObj) //,&op
	if err == nil {                                                    //替换成功
		//YsDbg.Debugf(nil,"DBDBG Mogo.UpdateOne success filter:%v data:%v",filter,updateObj)
		if ret.MatchedCount == 0 {
			return nil, mongo.ErrNoDocuments
		}

		return ret.UpsertedID, nil
	} else { //替换失败
		return nil, mongoErrConvert(err)
	}

}

// 删除指定key的数据
func (mgo *Mogo) DeleteOne(collection, key string, value interface{}) (int64, error) {
	col := mgo.GetCollection(collection)
	filter := bson.D{{Key: key, Value: value}}
	res, err := col.DeleteOne(context.Background(), filter)
	if err != nil {
		return 0, mongoErrConvert(err)
	}
	return res.DeletedCount, nil
}

//查询文档总数
//EstimatedDocumentCount

// 查询全部数据
// 网络异常 ErrMongoDeadlineExceeded
func (mgo *Mogo) FindAll(collection string, targets []interface{}) error {
	col := mgo.GetCollection(collection)
	filter := bson.D{}
	ctx := context.Background()
	cursor, err := col.Find(ctx, filter)
	if err != nil {
		return mongoErrConvert(err)
	} else {
		i := 0
		for cursor.Next(ctx) {
			cursor.Decode(targets[i])
			i++
		}
		return nil
	}
}

// 获取集合中的记录条数
func (mgo *Mogo) GetDocCnt(collection string, filter any) (int64, error) {
	col := mgo.GetCollection(collection)
	//filter := bson.D{}
	ctx := context.Background()
	re, err := col.CountDocuments(ctx, filter)
	return re, mongoErrConvert(err)
}

// 生成mongoDbId 不允许超过12位
func GenerateMongoDbId(name string) (primitive.ObjectID, error) {
	nameLen := len(name)
	if nameLen > 12 {
		return primitive.NilObjectID, errors.New("GenerateMongoDbId string length > 12")
	}
	var oid [12]byte
	copy(oid[:nameLen], []byte(name))

	// 将剩余部分填充为0
	for i := nameLen; i < 12; i++ {
		oid[i] = '0'
	}

	return oid, nil
}

// 24位16进制，转换为ObjectID
/*
func GenerateObjectID(hexStr string) (primitive.ObjectID, error) {
	if len(hexStr) > 24 {
		return primitive.NilObjectID, errors.New("hex string length must <= 16")
	}

	// 长度不足16时前面补0
	if len(hexStr) < 24 {
		hexStr = hexStr + "000000000000000000000000"[:24-len(hexStr)]
	}

	return primitive.ObjectIDFromHex(hexStr)
}*/
